﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace XData.Common.Fast
{
    internal class ExpressionHelper : IFastHelper
    {
        public Func<object[], T> CreateCreatorHandler<T>(ConstructorInfo constructorInfo)
        {
            if(constructorInfo == null)
            {
                throw new ArgumentNullException(nameof(constructorInfo));
            }
            var p = Expression.Parameter(typeof(object[]));
            ParameterInfo[] paramsTypes = constructorInfo.GetParameters();
            var pars = new Expression[paramsTypes.Length];
            for(int i = 0; i < paramsTypes.Length; i++)
            {
                Expression item = Expression.ArrayIndex(p, Expression.Constant(i));
                if(paramsTypes[i].ParameterType != item.Type)
                {
                    item = Expression.Convert(item, paramsTypes[i].ParameterType);
                }
                pars[i] = item;
            }

            var body = Expression.New(constructorInfo, pars);
            var lambda = Expression.Lambda<Func<object[], T>>(body, p);
            return lambda.Compile();
        }

        private Func<T, TValue> CreateGetterHandler<T, TValue>(MemberInfo memberInfo)
        {
            return CreateGetterExpression<T, TValue>(memberInfo)?.Compile();
        }

        private Action<T, TValue> CreateSetterHandler<T, TValue>(MemberInfo memberInfo)
        {
            var target = Expression.Parameter(typeof(T));
            var member = Expression.PropertyOrField(target, memberInfo.Name);
            if(member == null)
            {
                return null;
            }
            var val = Expression.Parameter(typeof(TValue));
            var body = Expression.Assign(member, val);
            var lambda = Expression.Lambda<Action<T, TValue>>(body, target, val);
            return lambda.Compile();
        }
        public Func<T, TValue> CreateGetterHandler<T, TValue>(PropertyInfo propertyInfo)
        {
            return CreateGetterHandler<T, TValue>((MemberInfo)propertyInfo);
        }
        public Action<T, TValue> CreateSetterHandler<T, TValue>(PropertyInfo propertyInfo)
        {
            return CreateSetterHandler<T, TValue>((MemberInfo)propertyInfo);
        }
        public Func<TValue> CreateStaticGetterHandler<TValue>(PropertyInfo propertyInfo)
        {
            var field = Expression.Property(null, propertyInfo);
            var lambda = Expression.Lambda<Func<TValue>>(field);
            return lambda.Compile();
        }
        public Action<TValue> CreateStaticSetterHandler<TValue>(PropertyInfo propertyInfo)
        {
            var field = Expression.Property(null, propertyInfo);
            var val = Expression.Parameter(typeof(TValue));
            var body = Expression.Assign(field, val);
            var lambda = Expression.Lambda<Action<TValue>>(body, val);
            return lambda.Compile();
        }
        public Func<TValue> CreateStaticGetterHandler<TValue>(FieldInfo fieldInfo)
        {
            var field = Expression.Field(null, fieldInfo);
            var lambda = Expression.Lambda<Func<TValue>>(field);
            return lambda.Compile();
        }
        public Action<TValue> CreateStaticSetterHandler<TValue>(FieldInfo fieldInfo)
        {
            var field = Expression.Field(null, fieldInfo);
            var val = Expression.Parameter(typeof(TValue));
            var body = Expression.Assign(field, val);
            var lambda = Expression.Lambda<Action<TValue>>(body, val);
            return lambda.Compile();
        }

        public Func<T, TValue> CreateGetterHandler<T, TValue>(FieldInfo fieldInfo)
        {
            return CreateGetterHandler<T, TValue>((MemberInfo)fieldInfo);
        }
        public Action<T, TValue> CreateSetterHandler<T, TValue>(FieldInfo fieldInfo)
        {
            return CreateSetterHandler<T, TValue>((MemberInfo)fieldInfo);
        }

        public static Expression<Func<T, TValue>> CreateGetterExpression<T, TValue>(MemberInfo memberInfo)
        {
            if(memberInfo == null)
            {
                throw new ArgumentNullException(nameof(memberInfo));
            }
            var type = typeof(T);
            if(memberInfo.DeclaringType != type)
            {
                memberInfo = type.GetMember(memberInfo.Name)?.First();
                if(memberInfo == null)
                {
                    throw new InvalidOperationException($"类型 {type.Name} 不包含名称为 {memberInfo.Name} 的属性或字段。");
                }
            }
            if(memberInfo is PropertyInfo pi && !pi.CanRead)
            {
                throw new InvalidOperationException($"属性 {pi.Name} 不支持读操作。");
            }
            var target = Expression.Parameter(type);
            Expression member = Expression.MakeMemberAccess(target, memberInfo);
            if(member.Type != typeof(TValue))
            {
                member = Expression.Convert(member, typeof(TValue));
            }
            return Expression.Lambda<Func<T, TValue>>(member, target);
        }

        public static LambdaExpression CreateGetterExpression(MemberInfo memberInfo, Type targetType)
        {
            if(memberInfo == null)
            {
                throw new ArgumentNullException(nameof(memberInfo));
            }
            if(memberInfo.DeclaringType != targetType)
            {
                memberInfo = targetType.GetMember(memberInfo.Name)?.First();
                if(memberInfo == null)
                {
                    throw new InvalidOperationException($"类型 {targetType.Name} 不包含名称为 {memberInfo.Name} 的属性或字段。");
                }
            }
            if(memberInfo is PropertyInfo pi && !pi.CanRead)
            {
                throw new InvalidOperationException($"属性 {pi.Name} 不支持读操作。");
            }
            var target = Expression.Parameter(targetType);
            Expression member = Expression.PropertyOrField(target, memberInfo.Name);
            if(member == null)
            {
                return null;
            }
            return Expression.Lambda(member, target);
        }
    }
}